﻿/* origin: FreeBSD /usr/src/lib/msun/src/s_exp2.c */
/*-
 * Copyright (c) 2005 David Schultz <das@FreeBSD.ORG>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

using static System.Math;

namespace GeographicLib
{
    partial class CMathManaged
    {
        private static readonly double[] exp2_tbl = new double[]
        {
            0.70710678118659198, 9.0705221111875289e-14,
            0.70902394216020825, 1.3322676295501878e-15,
            0.71094630108456136, -4.3465231414074879e-14,
            0.71287387205276065, 2.7311486405778851e-14,
            0.71480666919598435, -1.3322676295501878e-15,
            0.71674470668389423, -3.8857805861880479e-16,
            0.71868799872446298, -5.6565863104651726e-14,
            0.72063655956428752, -5.0681681074138396e-14,
            0.7225904034885321, 1.7541523789077473e-14,
            0.72454954482097433, -8.5931262105987116e-14,
            0.72651399792452254, -7.4384942649885488e-15,
            0.72848377720075019, 5.6010751592339147e-14,
            0.73045889709032796, 8.8262730457699945e-15,
            0.73243937207318144, -4.2299497238218464e-14,
            0.73442521666849014, -1.609823385706477e-15,
            0.73641644543468487, 2.1094237467877974e-15,
            0.73841307296974801, -3.219646771412954e-15,
            0.7404151139112507, 2.886579864025407e-14,
            0.74242258293637631, 1.1102230246251565e-16,
            0.74443549476217785, -4.0079051188968151e-14,
            0.74645386414562809, -8.3821838359199319e-15,
            0.74847770588359896, -3.6137759451548845e-14,
            0.75050703481324788, 6.7501559897209518e-14,
            0.75254186581171045, 1.3766765505351941e-14,
            0.75458221379669488, -3.1530333899354446e-14,
            0.756628093726329, 4.5852210917018965e-14,
            0.75867952059919352, 1.6386891843467311e-13,
            0.7607365094544245, 3.2640556923979602e-14,
            0.7627990753722339, -6.666889262874065e-14,
            0.76486723347362562, -3.3750779948604759e-14,
            0.76694099892045298, -4.7073456244106637e-14,
            0.76902038691582675, -3.219646771412954e-15,
            0.77110541270400679, 6.8056671409522096e-14,
            0.773196091570582, 1.3289369604763124e-13,
            0.77529243884247057, -5.4733995114020217e-14,
            0.77739446988852867, -2.8976820942716586e-14,
            0.77950220011894167, 4.29101199017623e-14,
            0.78161564498566505, -2.5479618415147343e-14,
            0.78373481998277117, -9.7144514654701197e-15,
            0.78585974064617325, 4.7184478546569153e-15,
            0.78799042255395446, 2.0539125955565396e-14,
            0.79012688132640618, -1.1102230246251565e-14,
            0.79226913262626775, 3.8025138593411612e-14,
            0.79441719215853734, -8.1046280797636427e-14,
            0.79657107567112928, -7.5495165674510645e-15,
            0.79873079895434207, 5.1514348342607263e-14,
            0.80089637784129808, -8.7541085491693593e-14,
            0.8030678282084277, 7.588374373312945e-14,
            0.80524516597464968, 4.035660694512444e-14,
            0.80742840710242858, -3.1086244689504383e-15,
            0.80961756759744374, 2.1149748619109232e-14,
            0.81181266350867554, 1.9761969838327786e-14,
            0.81401371092867125, -4.6629367034256575e-15,
            0.81622072599364404, 1.149080830487037e-14,
            0.81843372488350929, 4.7684078907650473e-14,
            0.8206527238219895, -2.3925306180672123e-14,
            0.82287773907698936, 1.2156942119645464e-14,
            0.82510878696035872, 8.7152507433074788e-14,
            0.82734588382810592, 1.5210055437364645e-14,
            0.82958904608081385, 1.0103029524088925e-14,
            0.83183829016334188, -4.5685677463325192e-14,
            0.83409363256531943, 4.8738790781044372e-14,
            0.83635508982079909, 1.3877787807814457e-15,
            0.83862267850894057, 2.2204460492503131e-15,
            0.84089641525368497, -5.0737192225369654e-14,
            0.84317631672420823, 1.9817480989559044e-14,
            0.84546239963468572, 5.6538107529036097e-14,
            0.84775468074467597, 1.6375789613221059e-14,
            0.85005317685938431, 2.0802803923913871e-13,
            0.85235790482897478, -8.6042284408449632e-14,
            0.85466888155023146, 8.3266726846886741e-17,
            0.85698612396497009, 1.1907141939104804e-14,
            0.85930964906123997, 1.6930901125533637e-15,
            0.86163947387313145, -9.2148511043887993e-15,
            0.86397561548091095, -1.3072876114961218e-14,
            0.86631809101116342, 1.3128387266192476e-14,
            0.8686669176368581, 8.2711615334574162e-15,
            0.87102211257756135, -2.7949864644938316e-14,
            0.87338369309957553, -1.4765966227514582e-14,
            0.87575167651592978, -1.532107773982716e-14,
            0.87812608018665728, 1.2378986724570495e-14,
            0.88050692151878507, -1.1213252548714081e-14,
            0.88289421796662804, -1.3683498778505054e-14,
            0.88528798703183276, 9.0233376326409598e-14,
            0.88768824626324638, -2.3148150063434514e-14,
            0.8900950132574994, -3.4491853817542051e-13,
            0.89250830565945305, -2.3342439092743916e-14,
            0.89492814116070096, 7.4940054162198066e-16,
            0.89735453750155836, 7.6605388699135801e-15,
            0.89978751247026978, 3.5804692544161298e-15,
            0.90222708390331463, 4.3021142204224816e-15,
            0.90467326968550965, -1.0019762797242038e-14,
            0.90712608775020653, 1.1379786002407855e-14,
            0.90958555607929203, -1.9428902930940239e-14,
            0.91205169270354303, 2.5895952049381776e-14,
            0.91452451570245363, 7.8270723236073536e-15,
            0.91700404320467233, 1.7208456881689926e-15,
            0.9194902933879413, -8.7152507433074788e-15,
            0.92198328447930478, -1.2809198146612744e-14,
            0.92448303475522842, 4.6074255521943996e-15,
            0.92698956254169729, 7.0082828429463007e-15,
            0.92950288621441202, 2.8310687127941492e-15,
            0.93202302419889149, -4.6906922790412864e-15,
            0.93454999497060232, -2.6145752229922437e-14,
            0.93708381705513721, -1.9609314172441827e-14,
            0.93962450902828798, 1.2115308756222021e-14,
            0.9421720895162452, 1.1940448629843559e-13,
            0.94472657719547781, 1.2614909117303341e-14,
            0.94728799079347548, -1.1185496973098452e-14,
            0.9498563490882761, -2.3314683517128287e-15,
            0.95243167090883007, -1.0644263248593688e-14,
            0.95501397513519204, -4.3159920082302961e-15,
            0.95760328069857525, 2.4216739724636227e-15,
            0.9601996065815368, 1.9630130854153549e-14,
            0.96280297181806562, 4.7323256424647298e-15,
            0.965413395493814, 6.1756155744774333e-16,
            0.96803089674614495, -3.3861802251067274e-15,
            0.97065549476431623, -5.8911209244172369e-15,
            0.97328720878958241, -5.0737192225369654e-14,
            0.97592605811548927, 1.8041124150158794e-16,
            0.97857206208769698, -4.649058915617843e-15,
            0.98122524010446421, 7.3552275381416621e-16,
            0.98388561161659194, 5.9362237347926339e-15,
            0.98655319612761638, -1.1310397063368782e-15,
            0.98922801319396725, -1.2011225347663412e-14,
            0.99191008242510936, -4.6837533851373792e-16,
            0.99459942348362285, -1.4982806662011683e-14,
            0.99729605608547245, 3.3597256920980811e-15,
            1, 0,
            1.0027112750502023, -2.6714741530042829e-16,
            1.0054299011127916, -1.6067008834497187e-14,
            1.0081558981184178, 3.6429192995512949e-16,
            1.0108892860517029, 3.5076108684251039e-15,
            1.013630084951489, -6.4531713306337224e-16,
            1.016378314910954, 1.3426759704060487e-15,
            1.0191339960777215, -2.3311214070176334e-14,
            1.0218971486541109, -8.2225892761300656e-15,
            1.0246677928971384, 3.858025010572419e-15,
            1.0274459491187771, 1.877664690397296e-14,
            1.030231637686039, -2.8449465006019636e-15,
            1.0330248790212302, 2.4702462297909733e-15,
            1.0358256936019519, -7.3135941747182187e-15,
            1.0386341019613787, -8.3266726846886741e-17,
            1.0414501246883212, 7.0221606307541151e-15,
            1.0442737824274095, -5.9466320756484947e-15,
            1.0471050958792909, 1.429412144204889e-15,
            1.0499440858006941, 9.3813845580825728e-15,
            1.052790773004622, -5.9535709695524019e-15,
            1.0556451783605751, 2.4577562207639403e-14,
            1.0585073227945059, -9.2842400434278716e-15,
            1.0613772272892525, -1.2961853812498703e-14,
            1.0642549128844674, 3.8441472227646045e-15,
            1.0671404006768259, 3.1086244689504383e-15,
            1.0700337118202567, 2.0192181260370035e-14,
            1.0729348675259776, 2.6922908347160046e-15,
            1.0758438890627808, -1.3683498778505054e-14,
            1.0787607977571219, 2.7616797737550769e-15,
            1.0816856149932175, 3.0253577421035516e-15,
            1.0846183622133163, 9.4091401336982017e-15,
            1.0875590609177761, 8.5348395018058909e-15,
            1.090507732665291, 4.4103609653234344e-14,
            1.0934643990728785, -9.6866958898544908e-15,
            1.0964290818163576, -2.5340840537069198e-14,
            1.0994018026302341, 1.5931700403370996e-14,
            1.102382583307852, 1.4460654895742664e-14,
            1.1053714457016148, -1.6500689703491389e-13,
            1.1083684117237071, 3.7109204598095857e-14,
            1.1113735033448087, -1.1601830607332886e-14,
            1.1143867425958942, 2.1649348980190553e-15,
            1.1174081515673715, 2.9698465908722937e-15,
            1.1204377524096127, 7.7993167479917247e-15,
            1.1234755673330008, -2.4397150966137815e-14,
            1.1265216186082829, 5.2541304640385533e-14,
            1.1295759285662892, 1.3322676295501878e-15,
            1.1326385195987572, 4.8322457146809938e-14,
            1.1357094141578237, 2.3064883336587627e-14,
            1.1387886347566736, -2.2898349882893854e-14,
            1.141876203969568, 8.0768725041480138e-15,
            1.1449721444317906, -1.712519015484304e-14,
            1.148076478840192, 1.6375789613221059e-14,
            1.1511892299529953, 1.5820678100908481e-14,
            1.154310420590267, 6.3699046037868357e-14,
            1.1574400736337362, -1.8429702208777599e-14,
            1.1605782120274846, -1.762479051592436e-14,
            1.1637248587775864, 1.0963452368173421e-14,
            1.166880036952455, -3.2834845953289005e-14,
            1.1700437696832879, 4.6546100307409688e-14,
            1.1732160801636253, -1.4654943925052066e-14,
            1.1763969916502701, -1.3683498778505054e-14,
            1.1795865274628723, -4.4686476741162551e-15,
            1.182784710984311, -3.6581848661398908e-14,
            1.1859915656609776, -1.9706458687096529e-14,
            1.1892071150026677, -6.4698246760030997e-14,
            1.1924313825823585, -9.5912167097367274e-13,
            1.1956643920398005, -3.2474023470285829e-14,
            1.1989061670743486, -3.8358205500799158e-14,
            1.2021567314527259, 2.7366997557010109e-14,
            1.2054161090051225, -1.5543122344752192e-15,
            1.2086843236265314, -5.9841021027295938e-14,
            1.2119613992768292, 3.3362201889985954e-14,
            1.2152473599804934, 2.9087843245179101e-14,
            1.2185422298273916, -1.9872992140790302e-14,
            1.2218460329727474, -1.1934897514720433e-14,
            1.2251587936370221, -1.4527268277220173e-13,
            1.2284805361068791, 1.0713652187632761e-14,
            1.2318112847341685, 1.0835776720341528e-13,
            1.2351510639369363, 3.4972025275692431e-15,
            1.2384998981997986, -2.0983215165415459e-14,
            1.241857812073518, 3.9412917374193057e-14,
            1.2452248301751068, -1.7513768213461844e-13,
            1.248600977189116, -1.0252909632413321e-13,
            1.2519862778663498, 3.858025010572419e-14,
            1.2553807570247149, 2.7311486405778851e-14,
            1.2587844395497014, -1.7208456881689926e-14,
            1.2621973503942812, 3.4805491821998658e-14,
            1.2656195145788114, 5.773159728050814e-15,
            1.2690509571917288, -5.0515147620444623e-15,
            1.2724917033893919, -1.2323475573339238e-14,
            1.2759417783963776, -1.6431300764452317e-14,
            1.2794012075057224, 5.9952043329758453e-14,
            1.2828700160787321, -5.1958437552457326e-14,
            1.2863482295460367, 1.2490009027033011e-14,
            1.2898358734066417, -2.6922908347160046e-14,
            1.2933329732290988, 1.0436096431476471e-14,
            1.2968395546509941, -1.7319479184152442e-14,
            1.3003556433796573, 7.3274719625260332e-15,
            1.3038812651919816, 5.0515147620444623e-14,
            1.307416445934654, -2.5646151868841116e-14,
            1.3109612115247846, 2.2315482794965646e-14,
            1.3145155879493446, -1.099120794378905e-14,
            1.3180796012660587, -5.773159728050814e-15,
            1.321653277603327, 1.8496315590255108e-13,
            1.325236643159704, -4.0578651550049472e-14,
            1.3288297242058946, -6.48925357893404e-14,
            1.3324325470833205, 1.7219559111936178e-13,
            1.3360451382041798, 3.6748382115092681e-14,
            1.3396675240534175, 1.2329026688462363e-13,
            1.3432997311867925, -4.5963233219481481e-14,
            1.3469417862329143, -3.3806291099836017e-14,
            1.3505937158922334, 2.1260770921571748e-13,
            1.3542555469368922, -5.5511151231257827e-16,
            1.3579273062129547, 5.6954441163270531e-14,
            1.3616090206381866, -4.0467629247586956e-14,
            1.3653007172041638, 1.6059376051202889e-13,
            1.3690024229745676, -2.4202861936828413e-14,
            1.3727141650876882, 2.0816681711721685e-14,
            1.376435970754569, 4.0800696154974503e-14,
            1.3801678672602453, 7.5495165674510645e-15,
            1.3839098819638134, -1.9373391779708982e-14,
            1.3876620422985131, -1.6708856520608606e-14,
            1.3914243757719233, -2.9420910152566648e-15,
            1.3951969099661583, -4.3298697960381105e-14,
            1.3989796725393029, 1.0227374502846942e-12,
            1.4027726912201248, -8.2156503822261584e-14,
            1.4065759938190179, 2.4980018054066022e-15,
            1.4103896082172265, -4.5241588253475129e-14
        };

        public override double Exp2(double x)
        {
            const double
                redux = 26388279066624.000,
                P1 = 0.69314718055994529,
                P2 = 0.24022650695910000,
                P3 = 0.055504108664821403,
                P4 = 0.0096181298421260664,
                P5 = 0.0013333559164630223;

            const double
                _0x1p52 = 4503599627370496.0;

            const int TBLSIZE = 256;

            double r, t, z;
            uint ix, i0;
            Bit64 u = x;
            Bit32 k = default;

            /* Filter out exceptional cases. */
            ix = (uint)(u.UInt64 >> 32 & 0x7fffffff);
            if (ix >= 0x408ff000)
            {  /* |x| >= 1022 or nan */
                if (ix >= 0x40900000 && u.UInt64 >> 63 == 0)
                {  /* x >= 1024 or nan */
                    /* overflow */
                    x *= _0x1p1023;
                    return x;
                }
                if (ix >= 0x7ff00000)  /* -inf or -nan */
                    return -1 / x;
                if ((u.UInt64 >> 63) != 0)
                {  /* x <= -1022 */
                    /* underflow */
                    if (x <= -1075 || x - _0x1p52 + _0x1p52 != x)
                        // _ = (float)(-_0x1p_149 / x);
                        return 0;
                    if (x <= -1075)
                        return 0;
                }
            }
            else if (ix < 0x3c900000)
            {  /* |x| < 0x1p-54 */
                return 1.0 + x;
            }

            /* Reduce x, computing z, i0, and k. */
            u.Double = x + redux;
            i0 = (uint)u.Int64;
            i0 += TBLSIZE / 2;
            k.UInt32 = i0 / TBLSIZE * TBLSIZE;
            k.Int32 /= TBLSIZE;
            i0 %= TBLSIZE;
            u.Double -= redux;
            z = x - u.Double;

            /* Compute r = exp2(y) = exp2t[i0] * p(z - eps[i]). */
            t = exp2_tbl[2 * i0];       /* exp2t[i0] */
            z -= exp2_tbl[2 * i0 + 1];  /* eps[i0]   */
            r = t + t * z * (P1 + z * (P2 + z * (P3 + z * (P4 + z * P5))));

            return ScaleB(r, k.Int32);
        }
    }
}
